package io.nio;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

@Slf4j
public class NIOClient {

    public static void main(String[] args) throws IOException {
        // 创建一个客户端SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        // 设置为非阻塞
        socketChannel.configureBlocking(false);
        // 创建selector
        Selector selector = Selector.open();
        // 创建reactor线程
        new Thread(new SingleReactorClient(socketChannel, selector)).start();


    }

    static class SingleReactorClient implements Runnable {

        private final SocketChannel socketChannel;

        private final Selector selector;

        public SingleReactorClient(SocketChannel socketChannel, Selector selector) {
            this.socketChannel = socketChannel;
            this.selector = selector;
        }

        @Override
        public void run() {
            try {
                // 链接到服务器
                doConnect(socketChannel, selector);
            } catch (Exception e) {
                log.error("客户端连接服务器失败", e);
                System.exit(1);
            }

            // 多路复用器执行多路复用程序
            while (true) {
                try {
                    selector.select(1000);
                    // 遍历就绪的key
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey selectionKey = iterator.next();
                        // 处理请求
                        handleSelectKey(selectionKey);
                        // 删除本次已处理的key，防止下次select重复处理
                        iterator.remove();
                    }

                } catch (IOException e) {
                    log.error("客户端多路复用程序执行失败", e);
                }
            }
        }


        private void doConnect(SocketChannel socketChannel, Selector selector) throws IOException {
            log.info("客户端开始连接服务器");
            boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
            if (connect) {
                log.info("客户端连接服务器成功");
                // 连接成功后，注册读事件
                socketChannel.register(selector, SelectionKey.OP_READ);
                // 发送消息
                socketChannel.write(ByteBuffer.wrap("你好，服务器".getBytes()));
                log.info("客户端发送消息成功--{}", "你好，服务器");
            } else {
                log.info("客户端连接服务器失败，注册连接事件");
                // 连接失败，注册连接事件
                socketChannel.register(selector, SelectionKey.OP_CONNECT);
            }
        }

        private void handleSelectKey(SelectionKey selectionKey) throws IOException {
            if (selectionKey.isValid()) {
                // 判断事件的类型来区别处理
                if (selectionKey.isConnectable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    // 判断是否连接成功
                    if (socketChannel.finishConnect()) {
                        log.info("客户端连接服务器成功");
                        // 连接成功后，注册读事件
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        // 发送消息
                        socketChannel.write(ByteBuffer.wrap("你好，服务器".getBytes()));
                        log.info("客户端发送消息成功--{}", "你好，服务器");
                    } else {
                        log.info("客户端连接服务器失败");
                        System.exit(1);
                    }
                }
                // 读事件
                if(selectionKey.isReadable()){
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    // 读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    socketChannel.read(buffer);
                    buffer.flip();
                    String msg = new String(buffer.array());
                    log.info("收到服务器消息：{}", msg);
                }

            }

        }
    }
}
